1   /*
2    * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  
27  /*
28   * The Original Code is HAT. The Initial Developer of the
29   * Original Code is Bill Foote, with contributions from others
30   * at JavaSoft/Sun.
31   */
32  
33  package com.sun.tools.hat.internal.model;
34  
35  import java.lang.ref.SoftReference;
36  import java.util.*;
37  import com.sun.tools.hat.internal.parser.ReadBuffer;
38  import com.sun.tools.hat.internal.util.Misc;
39  
40  /**
41   *
42   * @author      Bill Foote
43   */
44  
45  /**
46   * Represents a snapshot of the Java objects in the VM at one instant.
47   * This is the top-level "model" object read out of a single .hprof or .bod
48   * file.
49   */
50  
51  public class Snapshot {
52  
53      public static long SMALL_ID_MASK = 0x0FFFFFFFFL;
54      public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
55  
56      private static final JavaField[] EMPTY_FIELD_ARRAY = new JavaField[0];
57      private static final JavaStatic[] EMPTY_STATIC_ARRAY = new JavaStatic[0];
58  
59      // all heap objects
60      private Hashtable<Number, JavaHeapObject> heapObjects =
61                   new Hashtable<Number, JavaHeapObject>();
62  
63      private Hashtable<Number, JavaClass> fakeClasses =
64                   new Hashtable<Number, JavaClass>();
65  
66      // all Roots in this Snapshot
67      private Vector<Root> roots = new Vector<Root>();
68  
69      // name-to-class map
70      private Map<String, JavaClass> classes =
71                   new TreeMap<String, JavaClass>();
72  
73      // new objects relative to a baseline - lazily initialized
74      private volatile Map<JavaHeapObject, Boolean> newObjects;
75  
76      // allocation site traces for all objects - lazily initialized
77      private volatile Map<JavaHeapObject, StackTrace> siteTraces;
78  
79      // object-to-Root map for all objects
80      private Map<JavaHeapObject, Root> rootsMap =
81                   new HashMap<JavaHeapObject, Root>();
82  
83      // soft cache of finalizeable objects - lazily initialized
84      private SoftReference<Vector> finalizablesCache;
85  
86      // represents null reference
87      private JavaThing nullThing;
88  
89      // java.lang.ref.Reference class
90      private JavaClass weakReferenceClass;
91      // index of 'referent' field in java.lang.ref.Reference class
92      private int referentFieldIndex;
93  
94      // java.lang.Class class
95      private JavaClass javaLangClass;
96      // java.lang.String class
97      private JavaClass javaLangString;
98      // java.lang.ClassLoader class
99      private JavaClass javaLangClassLoader;
100 
101     // unknown "other" array class
102     private volatile JavaClass otherArrayType;
103     // Stuff to exclude from reachable query
104     private ReachableExcludes reachableExcludes;
105     // the underlying heap dump buffer
106     private ReadBuffer readBuf;
107 
108     // True iff some heap objects have isNew set
109     private boolean hasNewSet;
110     private boolean unresolvedObjectsOK;
111 
112     // whether object array instances have new style class or
113     // old style (element) class.
114     private boolean newStyleArrayClass;
115 
116     // object id size in the heap dump
117     private int identifierSize = 4;
118 
119     // minimum object size - accounts for object header in
120     // most Java virtual machines - we assume 2 identifierSize
121     // (which is true for Sun's hotspot JVM).
122     private int minimumObjectSize;
123 
124     public Snapshot(ReadBuffer buf) {
125         nullThing = new HackJavaValue("<null>", 0);
126         readBuf = buf;
127     }
128 
129     public void setSiteTrace(JavaHeapObject obj, StackTrace trace) {
130         if (trace != null && trace.getFrames().length != 0) {
131             initSiteTraces();
132             siteTraces.put(obj, trace);
133         }
134     }
135 
136     public StackTrace getSiteTrace(JavaHeapObject obj) {
137         if (siteTraces != null) {
138             return siteTraces.get(obj);
139         } else {
140             return null;
141         }
142     }
143 
144     public void setNewStyleArrayClass(boolean value) {
145         newStyleArrayClass = value;
146     }
147 
148     public boolean isNewStyleArrayClass() {
149         return newStyleArrayClass;
150     }
151 
152     public void setIdentifierSize(int size) {
153         identifierSize = size;
154         minimumObjectSize = 2 * size;
155     }
156 
157     public int getIdentifierSize() {
158         return identifierSize;
159     }
160 
161     public int getMinimumObjectSize() {
162         return minimumObjectSize;
163     }
164 
165     public void addHeapObject(long id, JavaHeapObject ho) {
166         heapObjects.put(makeId(id), ho);
167     }
168 
169     public void addRoot(Root r) {
170         r.setIndex(roots.size());
171         roots.addElement(r);
172     }
173 
174     public void addClass(long id, JavaClass c) {
175         addHeapObject(id, c);
176         putInClassesMap(c);
177     }
178 
179     JavaClass addFakeInstanceClass(long classID, int instSize) {
180         // Create a fake class name based on ID.
181         String name = "unknown-class<@" + Misc.toHex(classID) + ">";
182 
183         // Create fake fields convering the given instance size.
184         // Create as many as int type fields and for the left over
185         // size create byte type fields.
186         int numInts = instSize / 4;
187         int numBytes = instSize % 4;
188         JavaField[] fields = new JavaField[numInts + numBytes];
189         int i;
190         for (i = 0; i < numInts; i++) {
191             fields[i] = new JavaField("unknown-field-" + i, "I");
192         }
193         for (i = 0; i < numBytes; i++) {
194             fields[i + numInts] = new JavaField("unknown-field-" +
195                                                 i + numInts, "B");
196         }
197 
198         // Create fake instance class
199         JavaClass c = new JavaClass(name, 0, 0, 0, 0, fields,
200                                  EMPTY_STATIC_ARRAY, instSize);
201         // Add the class
202         addFakeClass(makeId(classID), c);
203         return c;
204     }
205 
206 
207     /**
208      * @return true iff it's possible that some JavaThing instances might
209      *          isNew set
210      *
211      * @see JavaThing.isNew()
212      */
213     public boolean getHasNewSet() {
214         return hasNewSet;
215     }
216 
217     //
218     // Used in the body of resolve()
219     //
220     private static class MyVisitor extends AbstractJavaHeapObjectVisitor {
221         JavaHeapObject t;
222         public void visit(JavaHeapObject other) {
223             other.addReferenceFrom(t);
224         }
225     }
226 
227     // To show heap parsing progress, we print a '.' after this limit
228     private static final int DOT_LIMIT = 5000;
229 
230     /**
231      * Called after reading complete, to initialize the structure
232      */
233     public void resolve(boolean calculateRefs) {
234         System.out.println("Resolving " + heapObjects.size() + " objects...");
235 
236         // First, resolve the classes.  All classes must be resolved before
237         // we try any objects, because the objects use classes in their
238         // resolution.
239         javaLangClass = findClass("java.lang.Class");
240         if (javaLangClass == null) {
241             System.out.println("WARNING:  hprof file does not include java.lang.Class!");
242             javaLangClass = new JavaClass("java.lang.Class", 0, 0, 0, 0,
243                                  EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
244             addFakeClass(javaLangClass);
245         }
246         javaLangString = findClass("java.lang.String");
247         if (javaLangString == null) {
248             System.out.println("WARNING:  hprof file does not include java.lang.String!");
249             javaLangString = new JavaClass("java.lang.String", 0, 0, 0, 0,
250                                  EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
251             addFakeClass(javaLangString);
252         }
253         javaLangClassLoader = findClass("java.lang.ClassLoader");
254         if (javaLangClassLoader == null) {
255             System.out.println("WARNING:  hprof file does not include java.lang.ClassLoader!");
256             javaLangClassLoader = new JavaClass("java.lang.ClassLoader", 0, 0, 0, 0,
257                                  EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
258             addFakeClass(javaLangClassLoader);
259         }
260 
261         for (JavaHeapObject t : heapObjects.values()) {
262             if (t instanceof JavaClass) {
263                 t.resolve(this);
264             }
265         }
266 
267         // Now, resolve everything else.
268         for (JavaHeapObject t : heapObjects.values()) {
269             if (!(t instanceof JavaClass)) {
270                 t.resolve(this);
271             }
272         }
273 
274         heapObjects.putAll(fakeClasses);
275         fakeClasses.clear();
276 
277         weakReferenceClass = findClass("java.lang.ref.Reference");
278         if (weakReferenceClass == null)  {      // JDK 1.1.x
279             weakReferenceClass = findClass("sun.misc.Ref");
280             referentFieldIndex = 0;
281         } else {
282             JavaField[] fields = weakReferenceClass.getFieldsForInstance();
283             for (int i = 0; i < fields.length; i++) {
284                 if ("referent".equals(fields[i].getName())) {
285                     referentFieldIndex = i;
286                     break;
287                 }
288             }
289         }
290 
291         if (calculateRefs) {
292             calculateReferencesToObjects();
293             System.out.print("Eliminating duplicate references");
294             System.out.flush();
295             // This println refers to the *next* step
296         }
297         int count = 0;
298         for (JavaHeapObject t : heapObjects.values()) {
299             t.setupReferers();
300             ++count;
301             if (calculateRefs && count % DOT_LIMIT == 0) {
302                 System.out.print(".");
303                 System.out.flush();
304             }
305         }
306         if (calculateRefs) {
307             System.out.println("");
308         }
309 
310         // to ensure that Iterator.remove() on getClasses()
311         // result will throw exception..
312         classes = Collections.unmodifiableMap(classes);
313     }
314 
315     private void calculateReferencesToObjects() {
316         System.out.print("Chasing references, expect "
317                          + (heapObjects.size() / DOT_LIMIT) + " dots");
318         System.out.flush();
319         int count = 0;
320         MyVisitor visitor = new MyVisitor();
321         for (JavaHeapObject t : heapObjects.values()) {
322             visitor.t = t;
323             // call addReferenceFrom(t) on all objects t references:
324             t.visitReferencedObjects(visitor);
325             ++count;
326             if (count % DOT_LIMIT == 0) {
327                 System.out.print(".");
328                 System.out.flush();
329             }
330         }
331         System.out.println();
332         for (Root r : roots) {
333             r.resolve(this);
334             JavaHeapObject t = findThing(r.getId());
335             if (t != null) {
336                 t.addReferenceFromRoot(r);
337             }
338         }
339     }
340 
341     public void markNewRelativeTo(Snapshot baseline) {
342         hasNewSet = true;
343         for (JavaHeapObject t : heapObjects.values()) {
344             boolean isNew;
345             long thingID = t.getId();
346             if (thingID == 0L || thingID == -1L) {
347                 isNew = false;
348             } else {
349                 JavaThing other = baseline.findThing(t.getId());
350                 if (other == null) {
351                     isNew = true;
352                 } else {
353                     isNew = !t.isSameTypeAs(other);
354                 }
355             }
356             t.setNew(isNew);
357         }
358     }
359 
360     public Enumeration<JavaHeapObject> getThings() {
361         return heapObjects.elements();
362     }
363 
364 
365     public JavaHeapObject findThing(long id) {
366         Number idObj = makeId(id);
367         JavaHeapObject jho = heapObjects.get(idObj);
368         return jho != null? jho : fakeClasses.get(idObj);
369     }
370 
371     public JavaHeapObject findThing(String id) {
372         return findThing(Misc.parseHex(id));
373     }
374 
375     public JavaClass findClass(String name) {
376         if (name.startsWith("0x")) {
377             return (JavaClass) findThing(name);
378         } else {
379             return classes.get(name);
380         }
381     }
382 
383     /**
384      * Return an Iterator of all of the classes in this snapshot.
385      **/
386     public Iterator getClasses() {
387         // note that because classes is a TreeMap
388         // classes are already sorted by name
389         return classes.values().iterator();
390     }
391 
392     public JavaClass[] getClassesArray() {
393         JavaClass[] res = new JavaClass[classes.size()];
394         classes.values().toArray(res);
395         return res;
396     }
397 
398     public synchronized Enumeration getFinalizerObjects() {
399         Vector obj;
400         if (finalizablesCache != null &&
401             (obj = finalizablesCache.get()) != null) {
402             return obj.elements();
403         }
404 
405         JavaClass clazz = findClass("java.lang.ref.Finalizer");
406         JavaObject queue = (JavaObject) clazz.getStaticField("queue");
407         JavaThing tmp = queue.getField("head");
408         Vector<JavaHeapObject> finalizables = new Vector<JavaHeapObject>();
409         if (tmp != getNullThing()) {
410             JavaObject head = (JavaObject) tmp;
411             while (true) {
412                 JavaHeapObject referent = (JavaHeapObject) head.getField("referent");
413                 JavaThing next = head.getField("next");
414                 if (next == getNullThing() || next.equals(head)) {
415                     break;
416                 }
417                 head = (JavaObject) next;
418                 finalizables.add(referent);
419             }
420         }
421         finalizablesCache = new SoftReference<Vector>(finalizables);
422         return finalizables.elements();
423     }
424 
425     public Enumeration<Root> getRoots() {
426         return roots.elements();
427     }
428 
429     public Root[] getRootsArray() {
430         Root[] res = new Root[roots.size()];
431         roots.toArray(res);
432         return res;
433     }
434 
435     public Root getRootAt(int i) {
436         return roots.elementAt(i);
437     }
438 
439     public ReferenceChain[]
440     rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) {
441         Vector<ReferenceChain> fifo = new Vector<ReferenceChain>();  // This is slow... A real fifo would help
442             // Must be a fifo to go breadth-first
443         Hashtable<JavaHeapObject, JavaHeapObject> visited = new Hashtable<JavaHeapObject, JavaHeapObject>();
444         // Objects are added here right after being added to fifo.
445         Vector<ReferenceChain> result = new Vector<ReferenceChain>();
446         visited.put(target, target);
447         fifo.addElement(new ReferenceChain(target, null));
448 
449         while (fifo.size() > 0) {
450             ReferenceChain chain = fifo.elementAt(0);
451             fifo.removeElementAt(0);
452             JavaHeapObject curr = chain.getObj();
453             if (curr.getRoot() != null) {
454                 result.addElement(chain);
455                 // Even though curr is in the rootset, we want to explore its
456                 // referers, because they might be more interesting.
457             }
458             Enumeration referers = curr.getReferers();
459             while (referers.hasMoreElements()) {
460                 JavaHeapObject t = (JavaHeapObject) referers.nextElement();
461                 if (t != null && !visited.containsKey(t)) {
462                     if (includeWeak || !t.refersOnlyWeaklyTo(this, curr)) {
463                         visited.put(t, t);
464                         fifo.addElement(new ReferenceChain(t, chain));
465                     }
466                 }
467             }
468         }
469 
470         ReferenceChain[] realResult = new ReferenceChain[result.size()];
471         for (int i = 0; i < result.size(); i++) {
472             realResult[i] =  result.elementAt(i);
473         }
474         return realResult;
475     }
476 
477     public boolean getUnresolvedObjectsOK() {
478         return unresolvedObjectsOK;
479     }
480 
481     public void setUnresolvedObjectsOK(boolean v) {
482         unresolvedObjectsOK = v;
483     }
484 
485     public JavaClass getWeakReferenceClass() {
486         return weakReferenceClass;
487     }
488 
489     public int getReferentFieldIndex() {
490         return referentFieldIndex;
491     }
492 
493     public JavaThing getNullThing() {
494         return nullThing;
495     }
496 
497     public void setReachableExcludes(ReachableExcludes e) {
498         reachableExcludes = e;
499     }
500 
501     public ReachableExcludes getReachableExcludes() {
502         return reachableExcludes;
503     }
504 
505     // package privates
506     void addReferenceFromRoot(Root r, JavaHeapObject obj) {
507         Root root = rootsMap.get(obj);
508         if (root == null) {
509             rootsMap.put(obj, r);
510         } else {
511             rootsMap.put(obj, root.mostInteresting(r));
512         }
513     }
514 
515     Root getRoot(JavaHeapObject obj) {
516         return rootsMap.get(obj);
517     }
518 
519     JavaClass getJavaLangClass() {
520         return javaLangClass;
521     }
522 
523     JavaClass getJavaLangString() {
524         return javaLangString;
525     }
526 
527     JavaClass getJavaLangClassLoader() {
528         return javaLangClassLoader;
529     }
530 
531     JavaClass getOtherArrayType() {
532         if (otherArrayType == null) {
533             synchronized(this) {
534                 if (otherArrayType == null) {
535                     addFakeClass(new JavaClass("[<other>", 0, 0, 0, 0,
536                                      EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY,
537                                      0));
538                     otherArrayType = findClass("[<other>");
539                 }
540             }
541         }
542         return otherArrayType;
543     }
544 
545     JavaClass getArrayClass(String elementSignature) {
546         JavaClass clazz;
547         synchronized(classes) {
548             clazz = findClass("[" + elementSignature);
549             if (clazz == null) {
550                 clazz = new JavaClass("[" + elementSignature, 0, 0, 0, 0,
551                                    EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
552                 addFakeClass(clazz);
553                 // This is needed because the JDK only creates Class structures
554                 // for array element types, not the arrays themselves.  For
555                 // analysis, though, we need to pretend that there's a
556                 // JavaClass for the array type, too.
557             }
558         }
559         return clazz;
560     }
561 
562     ReadBuffer getReadBuffer() {
563         return readBuf;
564     }
565 
566     void setNew(JavaHeapObject obj, boolean isNew) {
567         initNewObjects();
568         if (isNew) {
569             newObjects.put(obj, Boolean.TRUE);
570         }
571     }
572 
573     boolean isNew(JavaHeapObject obj) {
574         if (newObjects != null) {
575             return newObjects.get(obj) != null;
576         } else {
577             return false;
578         }
579     }
580 
581     // Internals only below this point
582     private Number makeId(long id) {
583         if (identifierSize == 4) {
584             return new Integer((int)id);
585         } else {
586             return new Long(id);
587         }
588     }
589 
590     private void putInClassesMap(JavaClass c) {
591         String name = c.getName();
592         if (classes.containsKey(name)) {
593             // more than one class can have the same name
594             // if so, create a unique name by appending
595             // - and id string to it.
596             name += "-" + c.getIdString();
597         }
598         classes.put(c.getName(), c);
599     }
600 
601     private void addFakeClass(JavaClass c) {
602         putInClassesMap(c);
603         c.resolve(this);
604     }
605 
606     private void addFakeClass(Number id, JavaClass c) {
607         fakeClasses.put(id, c);
608         addFakeClass(c);
609     }
610 
611     private synchronized void initNewObjects() {
612         if (newObjects == null) {
613             synchronized (this) {
614                 if (newObjects == null) {
615                     newObjects = new HashMap<JavaHeapObject, Boolean>();
616                 }
617             }
618         }
619     }
620 
621     private synchronized void initSiteTraces() {
622         if (siteTraces == null) {
623             synchronized (this) {
624                 if (siteTraces == null) {
625                     siteTraces = new HashMap<JavaHeapObject, StackTrace>();
626                 }
627             }
628         }
629     }
630 }